卡片的手势左右滑动的效果

要实现类似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>;
}
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容