import * as Haptics from 'expo-haptics'; import { LinearGradient } from 'expo-linear-gradient'; import React from 'react'; import { StyleSheet, Text, View, } from 'react-native'; import { PanGestureHandler, PanGestureHandlerGestureEvent, } from 'react-native-gesture-handler'; import Animated, { interpolate, interpolateColor, runOnJS, useAnimatedGestureHandler, useAnimatedStyle, useSharedValue, withSpring, } from 'react-native-reanimated'; interface MoodIntensitySliderProps { value: number; onValueChange: (value: number) => void; min?: number; max?: number; step?: number; width?: number; height?: number; } export default function MoodIntensitySlider({ value, onValueChange, min = 1, max = 10, step = 1, width = 320, height = 16, // 更粗的进度条 }: MoodIntensitySliderProps) { const thumbSize = 32; // 合适的触摸区域 const translateX = useSharedValue(0); const isDragging = useSharedValue(0); const sliderWidth = width - thumbSize; // thumb中心移动的有效范围 // 计算初始位置 React.useEffect(() => { const initialPosition = ((value - min) / (max - min)) * sliderWidth; translateX.value = withSpring(initialPosition); }, [value, min, max, sliderWidth, translateX]); const triggerHaptics = () => { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); }; const gestureHandler = useAnimatedGestureHandler< PanGestureHandlerGestureEvent, { startX: number; lastValue: number } >({ onStart: (_, context) => { context.startX = translateX.value; context.lastValue = value; isDragging.value = withSpring(1); runOnJS(triggerHaptics)(); }, onActive: (event, context) => { const newX = context.startX + event.translationX; const clampedX = Math.max(0, Math.min(sliderWidth, newX)); translateX.value = clampedX; // 计算当前值 const currentValue = Math.round((clampedX / sliderWidth) * (max - min) + min); // 当值改变时触发震动和回调 if (currentValue !== context.lastValue) { context.lastValue = currentValue; runOnJS(triggerHaptics)(); runOnJS(onValueChange)(currentValue); } }, onEnd: () => { // 计算最终值并吸附到最近的步长 const currentValue = Math.round((translateX.value / sliderWidth) * (max - min) + min); const snapPosition = ((currentValue - min) / (max - min)) * sliderWidth; translateX.value = withSpring(snapPosition); isDragging.value = withSpring(0); runOnJS(triggerHaptics)(); runOnJS(onValueChange)(currentValue); }, }); const thumbStyle = useAnimatedStyle(() => { const positionScale = interpolate( translateX.value, [0, sliderWidth], [1, 1.1] ); const dragScale = interpolate( isDragging.value, [0, 1], [1, 1.2] ); const finalScale = positionScale * dragScale; // 让thumb在滑动条中正确居中 const thumbPosition = translateX.value + thumbSize / 2; return { transform: [ { translateX: thumbPosition }, { scale: withSpring(finalScale) } ], }; }); const thumbInnerStyle = useAnimatedStyle(() => { const borderColor = interpolateColor( isDragging.value, [0, 1], ['#7a5af8', '#ff6b6b'] ); return { borderColor: borderColor, }; }); const progressStyle = useAnimatedStyle(() => { const progressWidth = translateX.value + thumbSize / 2; return { width: progressWidth, }; }); // 动态颜色配置 - 根据进度变化颜色 const getProgressColors = (progress: number) => { if (progress <= 0.25) { return ['#22c55e', '#84cc16'] as const; // 绿色到浅绿色 } else if (progress <= 0.5) { return ['#84cc16', '#eab308'] as const; // 浅绿色到黄色 } else if (progress <= 0.75) { return ['#eab308', '#f97316'] as const; // 黄色到橙色 } else { return ['#f97316', '#ef4444'] as const; // 橙色到红色 } }; const progressColorsStyle = useAnimatedStyle(() => { const progress = translateX.value / sliderWidth; return { backgroundColor: interpolateColor( progress, [0, 0.25, 0.5, 0.75, 1], ['#22c55e', '#84cc16', '#eab308', '#f97316', '#ef4444'] ), }; }); return ( {/* 背景轨道 - 更粗的灰色轨道 */} {/* 进度条 - 动态颜色 */} {/* 可拖拽的thumb */} {/* */} {/* 标签 */} 轻微 强烈 {/* 刻度 */} {Array.from({ length: max - min + 1 }, (_, i) => i + min).map((num) => ( ))} ); } const styles = StyleSheet.create({ container: { alignItems: 'center', paddingTop: 8, paddingBottom: 4, }, sliderContainer: { height: 10, justifyContent: 'center', position: 'relative', }, track: { position: 'absolute', left: 0, right: 0, borderRadius: 6, overflow: 'hidden', }, trackGradient: { flex: 1, borderRadius: 6, }, progress: { position: 'absolute', left: 0, borderRadius: 6, overflow: 'hidden', }, progressGradient: { flex: 1, borderRadius: 6, }, thumb: { position: 'absolute', left: 0, overflow: 'hidden', }, thumbGradient: { flex: 1, justifyContent: 'center', alignItems: 'center', }, thumbInner: { width: 16, height: 28, borderRadius: 8, backgroundColor: '#ffffff', borderWidth: 2, borderColor: '#7a5af8', }, valueContainer: { marginTop: 20, marginBottom: 12, paddingHorizontal: 16, paddingVertical: 10, backgroundColor: '#7a5af8', borderRadius: 16, shadowColor: '#7a5af8', shadowOffset: { width: 0, height: 3 }, shadowOpacity: 0.3, shadowRadius: 6, elevation: 6, }, valueText: { fontSize: 20, fontWeight: '800', color: '#ffffff', textAlign: 'center', minWidth: 28, }, labelsContainer: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 6, }, labelText: { fontSize: 12, color: '#5d6676', fontWeight: '500', }, scaleContainer: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 12, }, scaleItem: { alignItems: 'center', flex: 1, }, scaleMark: { width: 1.5, height: 8, backgroundColor: '#e5e7eb', borderRadius: 0.75, }, scaleMarkActive: { backgroundColor: '#7a5af8', width: 2, height: 10, }, });