Skip to content

Commit

Permalink
feat: add loop props,control wheter loop playback
Browse files Browse the repository at this point in the history
  • Loading branch information
dohooo committed Sep 8, 2021
1 parent 0e5c2f8 commit 97cf2b9
Show file tree
Hide file tree
Showing 8 changed files with 283 additions and 208 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Or if you use npm:
npm install react-native-reanimated-carousel
```

Now we need to install [`react-native-gesture-handler`](https://github.com/kmagiera/react-native-gesture-handler) and [`react-native-reanimated(>=2.0.0)`](https://github.com/kmagiera/react-native-reanimated).
Now we need to install [`react-native-gesture-handler`](https://github.com/kmagiera/react-native-gesture-handler) and [`react-native-reanimated(>=2.0.0)`](https://github.com/kmagiera/react-native-reanimated).
Don't use Expo to install reanimated it not supported `Reanimated(v2)`

## Usage
Expand Down Expand Up @@ -60,7 +60,8 @@ import Carousel from "react-native-reanimated-carousel";
| autoPlay | false | false | boolean | Auto play |
| autoPlayReverse | false | false | boolean | Auto play reverse playback |
| autoPlayInterval | false | 1000 | autoPlayInterval | Auto play playback interval |
| layout | false | defalut | 'default' | Carousel Animated transitions |
| mode | false | defalut | 'default'\|'parallax' | Carousel Animated transitions |
| loop | false | true | boolean | Carousel loop playback |
| parallaxScrollingOffset | false | 100 | number | When use 'default' Layout props,this prop can be control prev/next item offset |
| parallaxScrollingScale | false | 0.8 | number | When use 'default' Layout props,this prop can be control prev/next item scale |
| style | false | {} | ViewStyle | Carousel container style |
Expand Down
61 changes: 20 additions & 41 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,6 @@ const { width } = Dimensions.get('window');
export default function App() {
const r = React.useRef<ICarouselInstance | null>(null);


// return <View>
// <View style={{ position: "absolute" }}>
// <TouchableWithoutFeedback onPress={() => {
// console.log("red")
// }}>
// <View style={{ width: 200, height: 200, backgroundColor: "red" }}></View>
// </TouchableWithoutFeedback>
// </View>
// <View style={{ position: "absolute",top: 300+200*0.4,left:200*0.4 }}>
// <TouchableWithoutFeedback onPress={() => {
// console.log("blue")
// }}>
// <View style={{
// width: 200*0.2,
// height: 200*0.2,
// backgroundColor: "blue"
// }}></View>
// </TouchableWithoutFeedback>
// </View>
// <View style={{width:100,height:100,backgroundColor:"black"}}>

// </View>
// </View>

return (
<View
style={{
Expand All @@ -46,35 +21,39 @@ export default function App() {
>
<View style={{ height: 300 }}>
<Carousel<{ color: string }>
ref={r}
mode="parallax"
width={width}
data={[
{ color: 'red' },
{ color: 'purple' },
{ color: 'blue' },
{ color: 'pink' },
{ color: 'green' },
{ color: 'yellow' },
]}
parallaxScrollingScale={0.9}
parallaxScrollingScale={0.8}
renderItem={({ color }) => {
return (
<View
style={{
backgroundColor: color,
flex:1,
}}
>
<View
style={{
backgroundColor: color,
flex: 1,
}}
>
<TouchableWithoutFeedback
style={{flex:1}}
containerStyle={{flex:1}}
style={{ flex: 1 }}
containerStyle={{ flex: 1 }}
onPress={() => {
console.log(color);
}}
>
<View style={{flex:1,backgroundColor:color}}>

</View>
</TouchableWithoutFeedback>
</View>
<View
style={{
flex: 1,
backgroundColor: color,
}}
/>
</TouchableWithoutFeedback>
</View>
);
}}
/>
Expand Down
198 changes: 125 additions & 73 deletions src/Carousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import Animated, {
} from 'react-native-reanimated';
import { CarouselItem } from './CarouselItem';
import { fillNum } from './fillNum';
import type { TLayout } from './layouts';
import { DefaultLayout } from './layouts/index';
import type { TMode } from './layouts';
import { ParallaxLayout } from './layouts/index';
import { useCarouselController } from './useCarouselController';
import { useComputedAnim } from './useComputedAnim';

Expand All @@ -36,11 +36,16 @@ export const _withTiming = (

export interface ICarouselProps<T extends unknown> {
ref?: React.Ref<ICarouselInstance>;
/**
* Carousel loop playback.
* @default true
*/
loop?: boolean;
/**
* Carousel Animated transitions.
* @default 'default'
*/
layout?: TLayout;
mode?: TMode;
/**
* Render carousel item.
*/
Expand Down Expand Up @@ -101,7 +106,8 @@ function Carousel<T extends unknown = any>(
height = '100%',
data: _data = [],
width,
layout = 'default',
loop = true,
mode = 'default',
renderItem,
autoPlay = false,
autoPlayReverse = false,
Expand All @@ -121,52 +127,88 @@ function Carousel<T extends unknown = any>(
}
return _data;
}, [_data]);

const computedAnimResult = useComputedAnim(width, data.length);

const { next, prev } = useCarouselController({ width, handlerOffsetX });

const offsetX = useDerivedValue(() => {
const x = handlerOffsetX.value % computedAnimResult.WL;
return isNaN(x) ? 0 : x;
}, []);

const animatedListScrollHandler =
useAnimatedGestureHandler<PanGestureHandlerGestureEvent>({
onStart: (_, ctx: any) => {
ctx.startContentOffsetX = handlerOffsetX.value;
},
onActive: (e, ctx: any) => {
handlerOffsetX.value =
ctx.startContentOffsetX + Math.round(e.translationX);
},
onEnd: (e) => {
const intTranslationX = Math.round(e.translationX);
const sub = Math.abs(intTranslationX);

if (intTranslationX > 0) {
if (sub > width / 2) {
handlerOffsetX.value = _withTiming(
fillNum(width, handlerOffsetX.value + (width - sub))
);
} else {
handlerOffsetX.value = _withTiming(
fillNum(width, handlerOffsetX.value - sub)
);
useAnimatedGestureHandler<PanGestureHandlerGestureEvent>(
{
onStart: (_, ctx: any) => {
ctx.startContentOffsetX = handlerOffsetX.value;
},
onActive: (e, ctx: any) => {
if (loop) {
handlerOffsetX.value =
ctx.startContentOffsetX +
Math.round(e.translationX);
return;
}
return;
}

if (intTranslationX < 0) {
if (sub > width / 2) {
handlerOffsetX.value = _withTiming(
fillNum(width, handlerOffsetX.value - (width - sub))
);
} else {
handlerOffsetX.value = _withTiming(
fillNum(width, handlerOffsetX.value + sub)
);
handlerOffsetX.value = Math.max(
Math.min(
ctx.startContentOffsetX +
Math.round(e.translationX),
0
),
-(data.length - 1) * width
);
},
onEnd: (e) => {
const intTranslationX = Math.round(e.translationX);
const sub = Math.abs(intTranslationX);

if (intTranslationX > 0) {
if (!loop && handlerOffsetX.value >= 0) {
return;
}

if (sub > width / 2) {
handlerOffsetX.value = _withTiming(
fillNum(
width,
handlerOffsetX.value + (width - sub)
)
);
} else {
handlerOffsetX.value = _withTiming(
fillNum(width, handlerOffsetX.value - sub)
);
}
return;
}

if (intTranslationX < 0) {
if (
!loop &&
handlerOffsetX.value <= -(data.length - 1) * width
) {
return;
}

if (sub > width / 2) {
handlerOffsetX.value = _withTiming(
fillNum(
width,
handlerOffsetX.value - (width - sub)
)
);
} else {
handlerOffsetX.value = _withTiming(
fillNum(width, handlerOffsetX.value + sub)
);
}
return;
}
return;
}
},
},
});
[loop]
);

React.useImperativeHandle(ref, () => {
return {
Expand All @@ -189,14 +231,48 @@ function Carousel<T extends unknown = any>(
};
}, [autoPlay, autoPlayReverse, autoPlayInterval, prev, next]);

const Layouts = React.useMemo(() => {
switch (layout) {
case 'default':
return DefaultLayout;
const Layouts = React.useMemo<React.FC<{ index: number }>>(() => {
switch (mode) {
case 'parallax':
return ({ index, children }) => (
<ParallaxLayout
parallaxScrollingOffset={parallaxScrollingOffset}
parallaxScrollingScale={parallaxScrollingScale}
computedAnimResult={computedAnimResult}
width={width}
handlerOffsetX={offsetX}
index={index}
key={index}
loop={loop}
>
{children}
</ParallaxLayout>
);
default:
return undefined;
return ({ index, children }) => (
<CarouselItem
computedAnimResult={computedAnimResult}
width={width}
height={height}
handlerOffsetX={offsetX}
index={index}
key={index}
loop={loop}
>
{children}
</CarouselItem>
);
}
}, [layout]);
}, [
loop,
mode,
computedAnimResult,
height,
offsetX,
parallaxScrollingOffset,
parallaxScrollingScale,
width,
]);

return (
<PanGestureHandler onHandlerStateChange={animatedListScrollHandler}>
Expand All @@ -214,33 +290,9 @@ function Carousel<T extends unknown = any>(
>
{data.map((item, index) => {
return (
<CarouselItem
computedAnimResult={computedAnimResult}
width={width}
height={height}
handlerOffsetX={offsetX}
index={index}
key={index}
>
{Layouts ? (
<Layouts
parallaxScrollingOffset={
parallaxScrollingOffset
}
parallaxScrollingScale={
parallaxScrollingScale
}
computedAnimResult={computedAnimResult}
width={width}
handlerOffsetX={offsetX}
index={index}
>
{renderItem(item, index)}
</Layouts>
) : (
renderItem(item, index)
)}
</CarouselItem>
<Layouts index={index} key={index}>
{renderItem(item, index)}
</Layouts>
);
})}
</Animated.View>
Expand Down
10 changes: 9 additions & 1 deletion src/CarouselItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { IComputedAnimResult } from './useComputedAnim';
import { useOffsetX } from './useOffsetX';

export const CarouselItem: React.FC<{
loop?: boolean;
index: number;
handlerOffsetX: Animated.SharedValue<number>;
width: number;
Expand All @@ -18,8 +19,15 @@ export const CarouselItem: React.FC<{
width,
height = '100%',
computedAnimResult,
loop,
} = props;
const x = useOffsetX({ handlerOffsetX, index, width, computedAnimResult });
const x = useOffsetX({
handlerOffsetX,
index,
width,
computedAnimResult,
loop,
});
const offsetXStyle = useAnimatedStyle(() => {
return {
transform: [{ translateX: x.value - index * width }],
Expand Down
Loading

0 comments on commit 97cf2b9

Please sign in to comment.