/** * 可滑动的消息气泡容器 * 实现滑动回复手势功能 - 严格单向滑动,无抖动 */ import React, { useRef, useCallback } from 'react'; import { View, StyleSheet, Animated } from 'react-native'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import { PanGestureHandler, State } from 'react-native-gesture-handler'; import * as Haptics from 'expo-haptics'; import { useAppColors } from '../../../../theme'; // 滑动阈值 const SWIPE_THRESHOLD = 30; const MAX_SWIPE_DISTANCE = 50; interface SwipeableMessageBubbleProps { children: React.ReactNode; isMe: boolean; onReply: () => void; enabled?: boolean; } export const SwipeableMessageBubble: React.FC = ({ children, isMe, onReply, enabled = true, }) => { const colors = useAppColors(); const translateX = useRef(new Animated.Value(0)).current; const hasTriggeredRef = useRef(false); // 处理手势事件 - 使用原生驱动,不干预值 const onGestureEvent = Animated.event( [{ nativeEvent: { translationX: translateX } }], { useNativeDriver: true } ); // 处理手势状态变化 const onHandlerStateChange = useCallback((event: any) => { const { nativeEvent } = event; if (nativeEvent.state === State.END) { const translationX = nativeEvent.translationX; // 只允许特定方向的滑动 const shouldTrigger = isMe ? translationX < -SWIPE_THRESHOLD // 自己消息只能向左滑 : translationX > SWIPE_THRESHOLD; // 对方消息只能向右滑 if (shouldTrigger && !hasTriggeredRef.current) { hasTriggeredRef.current = true; // 触觉反馈 Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); // 立即触发回复 onReply(); // 重置标记 setTimeout(() => { hasTriggeredRef.current = false; }, 300); } // 立即回到原位 translateX.setValue(0); } }, [isMe, onReply]); // 计算回复图标的透明度 - 只在允许的方向显示 const iconOpacity = translateX.interpolate({ inputRange: isMe ? [-MAX_SWIPE_DISTANCE, -SWIPE_THRESHOLD, 0, 10] : [-10, 0, SWIPE_THRESHOLD, MAX_SWIPE_DISTANCE], outputRange: isMe ? [1, 0.8, 0, 0] : [0, 0, 0.8, 1], extrapolate: 'clamp', }); // 计算消息的位移 - 限制在允许的方向 const clampedTranslateX = translateX.interpolate({ inputRange: isMe ? [-MAX_SWIPE_DISTANCE - 10, -MAX_SWIPE_DISTANCE, 0, 10] : [-10, 0, MAX_SWIPE_DISTANCE, MAX_SWIPE_DISTANCE + 10], outputRange: isMe ? [-MAX_SWIPE_DISTANCE, -MAX_SWIPE_DISTANCE, 0, 0] : [0, 0, MAX_SWIPE_DISTANCE, MAX_SWIPE_DISTANCE], extrapolate: 'clamp', }); // 如果禁用手势,直接返回子元素 if (!enabled) { return <>{children}; } return ( {/* 回复图标 - 在消息后面 */} {/* 可滑动的消息内容 */} {children} ); }; const styles = StyleSheet.create({ container: { position: 'relative', }, replyIconContainer: { position: 'absolute', top: '50%', marginTop: -15, width: 30, height: 30, borderRadius: 15, backgroundColor: 'rgba(255, 107, 53, 0.1)', alignItems: 'center', justifyContent: 'center', }, replyIconLeft: { left: 8, }, replyIconRight: { right: 8, }, }); export default SwipeableMessageBubble;