要实现类似QQ音乐的会员页面的图片,根据用户左右手势滑动图片,效果如下:

image.png
代码如下:
import { ScreenUtils } from "../../../utils";
import { Gesture, GestureDetector } from "react-native-gesture-handler";
import Animated, { Extrapolation, interpolate, useAnimatedStyle, useSharedValue, withTiming } from "react-native-reanimated";
import { runOnJS } from "react-native-worklets";
const CARD_MIN_SCALE = 0.62;
const CARD_OFFSET = 200;
/**
* 卡片的手势的左右滑动
*/
export function CardPageView({ defaultIndex = 0, pageAction }) {
const pageWidth = ScreenUtils.getScreenW();
const imageList = [
require("@assets/images/equity/manager_icon.png"),
require("@assets/images/equity/partner_icon.png"),
require("@assets/images/equity/icon_silver_assistant.png")
];
const maxIndex = imageList.length - 1;
const initialIndex = Math.max(0, Math.min(defaultIndex, maxIndex));
const currentIndex = useSharedValue(initialIndex);
const translateX = useSharedValue(0);
const panGesture = Gesture.Pan()
.activeOffsetX([-12, 12])
.onUpdate((event) => {
translateX.value = event.translationX;
})
.onEnd((event) => {
const shouldGoPrev = event.translationX > CARD_OFFSET || event.velocityX > 360;
const shouldGoNext = event.translationX < -CARD_OFFSET || event.velocityX < -360;
let nextIndex = currentIndex.value;
if (shouldGoPrev) {
nextIndex = Math.max(0, currentIndex.value - 1);
} else if (shouldGoNext) {
nextIndex = Math.min(maxIndex, currentIndex.value + 1);
}
if (nextIndex != currentIndex.value) {
runOnJS(pageAction)(nextIndex);
}
currentIndex.value = nextIndex;
// translateX.value = withTiming(0, {
// duration: 220,
// });
translateX.value = 0;
});
return <GestureDetector gesture={panGesture}>
<Animated.View
style={[
{
width: pageWidth,
height: 300,
flexDirection: "row",
alignItems: 'center',
justifyContent: 'center',
},
]}>
{imageList.map((item, index) => {
return <AnimateItemView
key={index}
index={index}
item={item}
pageWidth={pageWidth}
translateX={translateX}
currentIndex={currentIndex}
/>;
})}
</Animated.View>
</GestureDetector>;
}
function AnimateItemView({ index, item, pageWidth, translateX, currentIndex }) {
const itemAnimationStyle = useAnimatedStyle(() => {
const progress = Math.max(-1, Math.min(1, translateX.value / CARD_OFFSET));//控制手势距离
const isCurrent = currentIndex.value === index;
const isPrev = currentIndex.value - 1 === index;//当前的前一个
const isNext = currentIndex.value + 1 === index;//当前的后一个
const isSwipingToPrev = progress > 0;
const isSwipingToNext = progress < 0;
console.log("======= progress =====", translateX.value, progress)
//手势往左边移动是负数,往右边移动是正数
if (isCurrent) {
const scale = interpolate(
progress,
[-1, 0, 1],
[CARD_MIN_SCALE, 1, CARD_MIN_SCALE],
Extrapolation.CLAMP,
);
const offsetX = interpolate(
progress,
[-1, 0, 1],
[-pageWidth, 0, pageWidth],
Extrapolation.CLAMP,
);
const opacity = interpolate(
Math.abs(progress),
[0, 1],
[1, 0.6],
Extrapolation.CLAMP,
);
return {
transform: [
{
scale,
},
{
translateX: offsetX,
},
],
opacity,
};
}
if (isNext && isSwipingToNext) {
return {
transform: [
{
scale: interpolate(
progress,
[-1, 0],
[1, CARD_MIN_SCALE],
Extrapolation.CLAMP,
),
},
{
translateX: interpolate(
progress,
[-1, 0],
[0, pageWidth],
Extrapolation.CLAMP,
),
},
],
opacity: interpolate(
progress,
[-1, 0],
[1, 0.45],
Extrapolation.CLAMP,
),
};
}
if (isPrev && isSwipingToPrev) {
return {
transform: [
{
scale: interpolate(
progress,
[0, 1],
[CARD_MIN_SCALE, 1],
Extrapolation.CLAMP,
),
},
{
translateX: interpolate(
progress,
[0, 1],
[-pageWidth, 0],
Extrapolation.CLAMP,
),
},
],
opacity: interpolate(
progress,
[0, 1],
[0.45, 1],
Extrapolation.CLAMP,
),
};
}
return {
transform: [
{
scale: CARD_MIN_SCALE,
},
{
translateX: index < currentIndex.value ? -pageWidth : pageWidth,
},
],
opacity: 0,
};
});
return <Animated.View
style={[{
width: 300,
height: 300,
alignItems: 'center',
justifyContent: 'center',
position: "absolute",
// backgroundColor: index == 0 ? 'red' : "green"
}, itemAnimationStyle]}>
<Animated.Image key={index} source={item}
style={{ width: 300, height: 299 }}
resizeMode={"stretch"} />
</Animated.View>;
}