Skip to content

Commit

Permalink
fix: fix useCarouselController
Browse files Browse the repository at this point in the history
  • Loading branch information
gxxgcn committed Jan 12, 2022
1 parent af33488 commit f5dc6dc
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 97 deletions.
189 changes: 131 additions & 58 deletions example/src/anim-tab-bar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,80 +1,153 @@
import * as React from 'react';
import { Dimensions } from 'react-native';
import { Extrapolate, interpolate } from 'react-native-reanimated';
import Carousel from 'react-native-reanimated-carousel';
import { View, Text } from 'react-native-ui-lib';
import type { TAnimationStyle } from '../../../src/layouts/BaseLayout';
import { Dimensions, Pressable } from 'react-native';
import Animated, {
Extrapolate,
interpolate,
interpolateColor,
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated';
import Carousel, { ICarouselInstance } from 'react-native-reanimated-carousel';
import { Colors, View } from 'react-native-ui-lib';
import SButton from '../components/SButton';
import { ElementsText } from '../constants';
import { useToggleButton } from '../hooks/useToggleButton';

const window = Dimensions.get('window');
const PAGE_WIDTH = 40;
const PAGE_WIDTH = 60;
const PAGE_HEIGHT = 40;
const DATA = ['鍛ㄤ竴', '鍛ㄤ簩', '鍛ㄤ笁', '鍛ㄥ洓', '鍛ㄤ簲', '鍛ㄥ叚', '鍛ㄦ棩'];

function Index() {
const r = React.useRef<ICarouselInstance>(null);
const AutoPLay = useToggleButton({
defaultValue: false,
buttonTitle: ElementsText.AUTOPLAY,
});

const animationStyle: TAnimationStyle = React.useCallback(
(value: number) => {
'worklet';

const translateX = interpolate(
value,
[-1, 0, 1],
[-PAGE_WIDTH, 0, PAGE_WIDTH]
);

const opacity = interpolate(
value,
[-1, 0, 1],
[0.5, 1, 0.5],
Extrapolate.CLAMP
);

const scale = interpolate(
value,
[-1, 0, 1],
[0.8, 1.4, 0.8],
Extrapolate.CLAMP
);

return {
transform: [{ translateX }, { scale }],
opacity,
};
},
[]
);

return (
<View style={{ flex: 1 }}>
<Carousel
loop={false}
<View style={{ marginVertical: 100 }}>
<Carousel
ref={r}
loop={false}
style={{
width: window.width,
height: PAGE_HEIGHT,
justifyContent: 'center',
alignItems: 'center',
borderBottomWidth: 1,
borderBottomColor: Colors.blue30,
}}
width={PAGE_WIDTH}
height={PAGE_HEIGHT}
data={DATA}
renderItem={({ item, animationValue }) => {
return (
<Item
animationValue={animationValue}
label={item}
onPress={() =>
r.current?.scrollTo(animationValue.value)
}
/>
);
}}
autoPlay={AutoPLay.status}
/>
</View>
{AutoPLay.button}
<View
style={{
width: window.width,
justifyContent: 'center',
alignItems: 'center',
paddingVertical: 110,
}}
width={PAGE_WIDTH}
height={PAGE_HEIGHT}
data={['鍛ㄤ竴', '鍛ㄤ簩', '鍛ㄤ笁', '鍛ㄥ洓', '鍛ㄤ簲', '鍛ㄥ叚', '鍛ㄦ棩']}
renderItem={({ item }) => {
return (
<View center height={'100%'}>
<Text color={'#26292E'}>{item}</Text>
</View>
);
marginTop: 24,
flexDirection: 'row',
justifyContent: 'space-evenly',
}}
autoPlay={AutoPLay.status}
customAnimation={animationStyle}
/>
{AutoPLay.button}
>
<SButton onPress={() => r.current?.prev()}>{'Prev'}</SButton>
<SButton onPress={() => r.current?.next()}>{'Next'}</SButton>
</View>
</View>
);
}

export default Index;

interface Props {
animationValue: Animated.SharedValue<number>;
label: string;
onPress?: () => void;
}

const Item: React.FC<Props> = (props) => {
const { animationValue, label, onPress } = props;

const translateY = useSharedValue(0);

const containerStyle = useAnimatedStyle(() => {
const opacity = interpolate(
animationValue.value,
[-1, 0, 1],
[0.5, 1, 0.5],
Extrapolate.CLAMP
);

return {
opacity,
};
}, [animationValue]);

const labelStyle = useAnimatedStyle(() => {
const scale = interpolate(
animationValue.value,
[-1, 0, 1],
[1, 1.25, 1],
Extrapolate.CLAMP
);

const color = interpolateColor(
animationValue.value,
[-1, 0, 1],
[Colors.grey30, Colors.blue30, Colors.grey30]
);

return {
transform: [{ scale }, { translateY: translateY.value }],
color,
};
}, [animationValue, translateY]);

const onPressIn = React.useCallback(() => {
translateY.value = withTiming(-8, { duration: 250 });
}, [translateY]);

const onPressOut = React.useCallback(() => {
translateY.value = withTiming(0, { duration: 250 });
}, [translateY]);

return (
<Pressable
onPress={onPress}
onPressIn={onPressIn}
onPressOut={onPressOut}
>
<Animated.View
style={[
{
height: '100%',
alignItems: 'center',
justifyContent: 'center',
},
containerStyle,
]}
>
<Animated.Text
style={[{ fontSize: 18, color: '#26292E' }, labelStyle]}
>
{label}
</Animated.Text>
</Animated.View>
</Pressable>
);
};
3 changes: 2 additions & 1 deletion src/Carousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,9 @@ function Carousel<T>(
prev,
getCurrentIndex,
goToIndex,
scrollTo: carouselController.scrollTo,
}),
[getCurrentIndex, goToIndex, next, prev]
[getCurrentIndex, goToIndex, next, prev, carouselController.scrollTo]
);

const visibleRanges = useVisibleRanges({
Expand Down
109 changes: 73 additions & 36 deletions src/hooks/useCarouselController.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface ICarouselController {
computedIndex: () => void;
getCurrentIndex: () => number;
to: (index: number, animated?: boolean) => void;
scrollTo: (animationValue: number, animated?: boolean) => void;
}

export function useCarouselController(opts: IOpts): ICarouselController {
Expand All @@ -46,6 +47,19 @@ export function useCarouselController(opts: IOpts): ICarouselController {
const sharedIndex = React.useRef<number>(0);
const sharedPreIndex = React.useRef<number>(0);

const currentFixedPage = React.useCallback(() => {
if (loop) {
return -Math.round(handlerOffsetX.value / size);
}

const fixed = (handlerOffsetX.value / size) % length;
return Math.round(
handlerOffsetX.value <= 0
? Math.abs(fixed)
: Math.abs(fixed > 0 ? length - fixed : 0)
);
}, [handlerOffsetX, length, size, loop]);

const convertToSharedIndex = React.useCallback(
(i: number) => {
if (loop) {
Expand Down Expand Up @@ -100,12 +114,11 @@ export function useCarouselController(opts: IOpts): ICarouselController {
}, [opts]);

const scrollWithTiming = React.useCallback(
(toValue: number, callback?: () => void) => {
(toValue: number) => {
return withTiming(
toValue,
{ duration, easing: Easing.easeOutQuart },
(isFinished: boolean) => {
callback?.();
if (isFinished) {
runOnJS(onScrollEnd)();
}
Expand All @@ -115,42 +128,54 @@ export function useCarouselController(opts: IOpts): ICarouselController {
[onScrollEnd, duration]
);

const next = React.useCallback(() => {
if (!canSliding() || (!loop && index.value === length - 1)) return;
const next = React.useCallback(
(n = 1, animated = true) => {
if (!canSliding() || (!loop && index.value >= length - 1)) return;

onScrollBegin?.();

const currentPage = Math.round(handlerOffsetX.value / size);

handlerOffsetX.value = scrollWithTiming((currentPage - 1) * size);
}, [
canSliding,
loop,
index.value,
length,
onScrollBegin,
handlerOffsetX,
size,
scrollWithTiming,
]);
onScrollBegin?.();

const prev = React.useCallback(() => {
if (!canSliding() || (!loop && index.value === 0)) return;
const nextPage = currentFixedPage() + n;
index.value = nextPage;
handlerOffsetX.value = animated
? scrollWithTiming(-nextPage * size)
: -nextPage * size;
},
[
canSliding,
loop,
index,
length,
onScrollBegin,
handlerOffsetX,
size,
scrollWithTiming,
currentFixedPage,
]
);

onScrollBegin?.();
const prev = React.useCallback(
(n = 1, animated = true) => {
if (!canSliding() || (!loop && index.value <= 0)) return;

const currentPage = Math.round(handlerOffsetX.value / size);
onScrollBegin?.();

handlerOffsetX.value = scrollWithTiming((currentPage + 1) * size);
}, [
canSliding,
loop,
index.value,
onScrollBegin,
handlerOffsetX,
size,
scrollWithTiming,
]);
const prevPage = currentFixedPage() - n;
index.value = prevPage;
handlerOffsetX.value = animated
? scrollWithTiming(-prevPage * size)
: -prevPage * size;
},
[
canSliding,
loop,
index,
onScrollBegin,
handlerOffsetX,
size,
scrollWithTiming,
currentFixedPage,
]
);

const to = React.useCallback(
(idx: number, animated: boolean = false) => {
Expand All @@ -162,9 +187,8 @@ export function useCarouselController(opts: IOpts): ICarouselController {
const offset = handlerOffsetX.value + (index.value - idx) * size;

if (animated) {
handlerOffsetX.value = scrollWithTiming(offset, () => {
index.value = idx;
});
index.value = idx;
handlerOffsetX.value = scrollWithTiming(offset);
} else {
handlerOffsetX.value = offset;
index.value = idx;
Expand All @@ -182,10 +206,23 @@ export function useCarouselController(opts: IOpts): ICarouselController {
]
);

const scrollTo = React.useCallback(
(value: number, animated = true) => {
const n = Math.round(value);
if (n < 0) {
prev(Math.abs(n), animated);
} else {
next(n, animated);
}
},
[prev, next]
);

return {
next,
prev,
to,
scrollTo,
index,
length,
sharedIndex,
Expand Down
Loading

0 comments on commit f5dc6dc

Please sign in to comment.