HarmonyOS APP开发---"心动卡"社交匹配App,需要用到这个库

HarmonyOS APP开发---"心动卡"社交匹配App,需要用到这个库

做一个社交匹配 App,用户左右滑卡片决定喜欢/跳过,滑出的卡片要有弹性回弹或飞出动画,下一张卡片从后方升起。Animated API跑在JS线程,滑多了就卡------@react-native-ohos/react-native-reanimated 在 UI 线程跑动画,配合手势零延迟跟手。
📦 仓库地址:gitcode.com/CPF-RN/rntp... | 安装:npm install @react-native-ohos/react-native-reanimated


写在前面

"心动卡"的核心交互是"Tinder 式卡片滑动":

  • 用户右滑 = 喜欢,卡片向右飞出 + 淡出
  • 用户左滑 = 跳过,卡片向左飞出 + 淡出
  • 滑动不够远 = 弹簧回弹原位
  • 卡片飞出后,下一张从后方升起 + 缩放进入
  • 滑动时卡片有旋转角度 + 透明度渐变 + 背景卡片放大

这些动画的痛点:

  • 手势跟手:拖拽卡片必须零延迟,慢半拍用户立刻能感觉到
  • 连续动画:弹簧回弹 + 飞出 + 下一张升起,多个动画串联
  • JS 线程忙:卡片里可能有图片在解码,JS 线程一忙动画就掉帧

react-native-reanimated 把动画计算移到 UI 线程 (原生层),用 SharedValue + worklet 不走 JS Bridge------60fps 丝滑,手势零延迟

这篇文章聊什么

  1. 卡片拖拽------Pan 手势 + SharedValue 驱动位移旋转
  2. 弹簧回弹 vs 飞出------判断滑动距离决定动画
  3. 下一张卡片升起------进入动画 + 堆叠效果
flowchart TD A[用户拖拽卡片] --> B[Gesture.Pan onUpdate] B --> C[SharedValue: translateX/rotate/scale] C --> D[UI线程实时渲染] D --> E{手指抬起} E -->|滑动> 阈值| F[withTiming 飞出] E -->|滑动< 阈值| G[withSpring 回弹] F --> H[触发 like/pass 回调] H --> I[下一张卡片 entering 动画] G --> J[回到原位]

第一步:安装

bash 复制代码
npm install @react-native-ohos/react-native-reanimated react-native-gesture-handler

babel 配置:

js 复制代码
plugins: ['react-native-reanimated/plugin']

第二步:卡片拖拽 + 旋转

用 Pan 手势驱动卡片的位移、旋转和透明度:

tsx 复制代码
import { GestureDetector, Gesture } from 'react-native-gesture-handler'
import Animated, {
  useSharedValue, useAnimatedStyle, withSpring, withTiming,
  runOnJS, interpolate, Extrapolate,
} from 'react-native-reanimated'

const SWIPE_THRESHOLD = 120 // 滑动超过 120px 触发飞出

function SwipeableCard({ profile, onSwipe, index }: {
  profile: Profile
  onSwipe: (direction: 'left' | 'right') => void
  index: number
}) {
  const translateX = useSharedValue(0)
  const translateY = useSharedValue(0)

  const pan = Gesture.Pan()
    .onUpdate((e) => {
      translateX.value = e.translationX
      translateY.value = e.translationY
    })
    .onEnd((e) => {
      if (Math.abs(translateX.value) > SWIPE_THRESHOLD) {
        // 飞出:向滑动方向加速飞出
        const direction = translateX.value > 0 ? 1 : -1
        translateX.value = withTiming(direction * 500, { duration: 300 }, () => {
          runOnJS(onSwipe)(direction > 0 ? 'right' : 'left')
        })
      } else {
        // 回弹:弹簧回到原位
        translateX.value = withSpring(0, { damping: 15, stiffness: 150 })
        translateY.value = withSpring(0, { damping: 15, stiffness: 150 })
      }
    })

  // 动画样式:位移 + 旋转 + 透明度渐变
  const animatedStyle = useAnimatedStyle(() => {
    const rotate = interpolate(
      translateX.value,
      [-200, 0, 200],
      [-15, 0, 15], // 最多旋转 15 度
      Extrapolate.CLAMP,
    )
    const opacity = interpolate(
      Math.abs(translateX.value),
      [0, 200],
      [1, 0.5], // 滑得越远越透明
      Extrapolate.CLAMP,
    )
    return {
      transform: [
        { translateX: translateX.value },
        { translateY: translateY.value },
        { rotate: `${rotate}deg` },
      ],
      opacity,
    }
  })

  return (
    <GestureDetector gesture={pan}>
      <Animated.View style={[styles.card, animatedStyle]}>
        {/* 卡片内容:头像、昵称、简介 */}
      </Animated.View>
    </GestureDetector>
  )
}

关键点

  • onUpdate 实时更新 SharedValue------手势在原生层识别,动画在 UI 线程执行,零延迟跟手
  • rotate 随位移插值------卡片拖拽时有"翻牌"的感觉
  • opacity 随位移渐变------滑得越远越透明,给用户"快飞出了"的视觉反馈

第三步:飞出 vs 回弹

手指抬起时判断滑动距离:

tsx 复制代码
.onEnd((e) => {
  if (Math.abs(translateX.value) > SWIPE_THRESHOLD) {
    // ===== 飞出 =====
    const direction = translateX.value > 0 ? 1 : -1
    translateX.value = withTiming(
      direction * 500, // 向滑动方向飞出 500px
      { duration: 300 },
      () => {
        // 动画完成后回调 JS 线程
        runOnJS(onSwipe)(direction > 0 ? 'right' : 'left')
      },
    )
  } else {
    // ===== 回弹 =====
    translateX.value = withSpring(0, {
      damping: 15,     // 适当阻尼,弹得不太多
      stiffness: 150,  // 弹簧刚度,回弹速度
    })
  }
})
  • 飞出withTiming 线性加速飞出,300ms 完成
  • 回弹withSpring 弹簧回弹,有物理感的弹动

全程在 UI 线程执行,JS 线程哪怕正在解码下一张卡片的图片,动画也丝滑不掉帧。


第四步:下一张卡片升起(堆叠效果)

多张卡片堆叠,后面的卡片随前一张的滑动距离放大上移:

tsx 复制代码
function CardStack({ profiles }: { profiles: Profile[] }) {
  const [currentIndex, setCurrentIndex] = useState(0)

  return (
    <View style={{ flex: 1, position: 'relative' }}>
      {/* 渲染当前卡片 + 后面 2 张(堆叠效果) */}
      {profiles.slice(currentIndex, currentIndex + 3).reverse().map((profile, i) => {
        const stackIndex = 2 - i // 0=当前, 1=下一张, 2=再下一张
        return (
          <BackgroundCard
            key={profile.id}
            profile={profile}
            stackIndex={stackIndex}
          />
        )
      })}

      {/* 最上层是可滑动的卡片 */}
      {profiles[currentIndex] && (
        <SwipeableCard
          profile={profiles[currentIndex]}
          onSwipe={() => setCurrentIndex((i) => i + 1)}
          index={currentIndex}
        />
      )}
    </View>
  )
}

// 后面的卡片随层级缩放 + 上移
function BackgroundCard({ profile, stackIndex }: { profile: Profile; stackIndex: number }) {
  const animatedStyle = useAnimatedStyle(() => ({
    transform: [
      { scale: 1 - stackIndex * 0.05 }, // 后面的卡片小 5%
      { translateY: stackIndex * -10 },   // 后面的卡片上移 10px
    ],
    opacity: stackIndex === 0 ? 1 : 0.9,
    zIndex: 10 - stackIndex,
  }))

  return <Animated.View style={[styles.card, animatedStyle]}>{/* ... */}</Animated.View>
}

为什么"心动卡"选了 Reanimated?

需求 RN Animated @react-native-ohos/react-native-reanimated
手势跟手 JS 线程,延迟 ✅ UI 线程,零延迟
JS 忙时 ❌ 掉帧 ✅ 不受影响
弹簧动画 ✅ 但 JS 线程 ✅ UI 线程弹簧
插值旋转 ✅ interpolate
连续动画 ❌ 难串联 ✅ withSequence
声明式进入 ✅ entering

总结

"心动卡"这个场景里,@react-native-ohos/react-native-reanimated 解决了三件事:

  1. 零延迟跟手------Pan 手势 + SharedValue 在 UI 线程驱动,拖拽像素级跟手
  2. 飞出/回弹 ------withTiming 飞出 + withSpring 回弹,物理感动画
  3. 堆叠效果------后面卡片随层级缩放上移,进入动画丝滑

如果你也在做卡片滑动、侧滑删除、拖拽排序等手势驱动的动画场景,react-native-reanimated 是 RN 鸿蒙动画性能的天花板。

相关推荐
光锥智能1 小时前
HUAWEI WATCH 6系列正式发布:鸿蒙AI手表,智慧健康旗舰
人工智能·华为·harmonyos
OH_TPC3 小时前
【鸿蒙优选三方库】@react-native-ohos/react-native-gesture-handler:原生级手势识别与处理框架
华为·harmonyos·鸿蒙
嘴贱欠吻!4 小时前
Flutter 触觉反馈插件 Gaimon三方库鸿蒙端教程
flutter·华为·harmonyos
Francek Chen4 小时前
【HarmonyOS 7】新品发布:华为Mate XT 2携麒麟9050 Pro与鸿蒙7重磅登场
人工智能·华为·harmonyos·鸿蒙·小艺
可乐鸡翅yeah_4 小时前
App WebView 加载 M3U8 流媒体踩坑,安卓 iOS 混合开发播放异常定位
android·ios·harmonyos·m3u8·m3u8在线播放
贾伟康4 小时前
【句匠|19】HarmonyOS ArkTS 回归测试实战:覆盖启动、空数据、异常输入和重复点击
自动化测试·harmonyos·arkts·回归测试·hypium
●VON5 小时前
Flutter 鸿蒙插件适配实战:给 screen_security 补上截图与录屏防护
flutter·华为·harmonyos
贾伟康10 小时前
【口算王|20】HarmonyOS ArkTS AppGallery 发布复查实战:核对包名、版本、设备、素材和离线声明
harmonyos·arkts·appgallery·应用发布·发布审核
ChinaDragon14 小时前
HarmonyOS:Web使用Dsbridge与JavaScript完成交互
harmonyos