Here is a fold
animation example. You can see the source code of this page here (opens in a new tab).
import * as React from "react";
import { StyleSheet, Text, View } from "react-native";
import { TouchableWithoutFeedback } from "react-native-gesture-handler";
import type Animated from "react-native-reanimated";
import { Extrapolate, interpolate } from "react-native-reanimated";
import Carousel, { TAnimationStyle } from "react-native-reanimated-carousel";
import { faker } from "@faker-js/faker";
import { SBImageItem } from "../../components/SBImageItem";
import SButton from "../../components/SButton";
import { ElementsText, window } from "../../constants";
const PAGE_WIDTH = window.width;
function Index() {
const [isFast, setIsFast] = React.useState(false);
const [isAutoPlay, setIsAutoPlay] = React.useState(false);
const itemSize = PAGE_WIDTH / 2;
const centerOffset = PAGE_WIDTH / 2 - itemSize / 2;
const dataLength = 18;
const sideItemCount = 3;
const sideItemWidth = (PAGE_WIDTH - itemSize) / (2 * sideItemCount);
const animationStyle: TAnimationStyle = React.useCallback(
(value: number) => {
"worklet";
const itemOffsetInput = new Array(sideItemCount * 2 + 1)
.fill(null)
.map((_, index) => index - sideItemCount);
const itemOffset = interpolate(
value,
// e.g. [0,1,2,3,4,5,6] -> [-3,-2,-1,0,1,2,3]
itemOffsetInput,
itemOffsetInput.map((item) => {
if (item < 0) {
return (-itemSize + sideItemWidth) * Math.abs(item);
}
else if (item > 0) {
return (
(itemSize - sideItemWidth) * (Math.abs(item) - 1)
);
}
return 0;
}) as number[],
);
const translate
= interpolate(value, [-1, 0, 1], [-itemSize, 0, itemSize])
+ centerOffset
- itemOffset;
const width = interpolate(
value,
[-1, 0, 1],
[sideItemWidth, itemSize, sideItemWidth],
Extrapolate.CLAMP,
);
return {
transform: [
{
translateX: translate,
},
],
width,
overflow: "hidden",
};
},
[centerOffset, itemSize, sideItemWidth, sideItemCount],
);
return (
<View style={{ flex: 1 }}>
<Carousel
width={itemSize}
height={PAGE_WIDTH / 2}
style={{
width: PAGE_WIDTH,
height: PAGE_WIDTH / 2,
backgroundColor: "black",
}}
loop
windowSize={Math.round(dataLength / 2)}
scrollAnimationDuration={1500}
autoPlay={isAutoPlay}
autoPlayInterval={isFast ? 100 : 1200}
data={[...new Array(dataLength).keys()]}
renderItem={({ index, animationValue }) => (
<Item
animationValue={animationValue}
index={index}
key={index}
/>
)}
customAnimation={animationStyle}
/>
<SButton
onPress={() => {
setIsFast(!isFast);
}}
>
{isFast ? "NORMAL" : "FAST"}
</SButton>
<SButton
onPress={() => {
setIsAutoPlay(!isAutoPlay);
}}
>
{ElementsText.AUTOPLAY}:{`${isAutoPlay}`}
</SButton>
</View>
);
}
const Item: React.FC<{
index: number
animationValue: Animated.SharedValue<number>
}> = ({ index }) => {
return (
<TouchableWithoutFeedback
onPress={() => {
console.log(index);
}}
containerStyle={{ flex: 1 }}
style={{ flex: 1 }}
>
<View
style={{
backgroundColor: "white",
flex: 1,
justifyContent: "center",
overflow: "hidden",
alignItems: "center",
}}
>
<SBImageItem
showIndex={false}
key={index}
style={styles.image}
index={index}
/>
<Text
style={{
color: "white",
fontWeight: "600",
fontSize: 40,
width: 100,
textAlign: "center",
}}
>
{faker.name.fullName().slice(0, 2).toUpperCase()}
</Text>
</View>
</TouchableWithoutFeedback>
);
};
const styles = StyleSheet.create({
image: {
position: "absolute",
width: "100%",
height: "100%",
borderRadius: 0,
},
});
export default Index;