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 translateX = useSharedValue(0); const sliderWidth = width - 40; // 减去thumb的宽度 const thumbSize = 36; // 更大的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; 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); runOnJS(triggerHaptics)(); runOnJS(onValueChange)(currentValue); }, }); const thumbStyle = useAnimatedStyle(() => { const scale = interpolate( translateX.value, [0, sliderWidth], [1, 1.05] ); return { transform: [ { translateX: translateX.value }, { scale: withSpring(scale) } ], }; }); 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', paddingVertical: 16, }, sliderContainer: { height: 50, justifyContent: 'center', position: 'relative', paddingHorizontal: 20, }, track: { position: 'absolute', left: 20, right: 20, borderRadius: 8, overflow: 'hidden', }, trackGradient: { flex: 1, borderRadius: 8, }, progress: { position: 'absolute', left: 20, borderRadius: 8, overflow: 'hidden', }, progressGradient: { flex: 1, borderRadius: 8, }, thumb: { position: 'absolute', left: 22, elevation: 6, overflow: 'hidden', }, thumbGradient: { flex: 1, justifyContent: 'center', alignItems: 'center', }, thumbInner: { width: 16, height: 32, borderRadius: 8, backgroundColor: '#ffffff', shadowColor: '#ffffff', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.3, shadowRadius: 2, elevation: 2, }, 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: 8, paddingHorizontal: 20, }, labelText: { fontSize: 14, color: '#5d6676', fontWeight: '600', }, scaleContainer: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 16, paddingHorizontal: 20, }, scaleItem: { alignItems: 'center', flex: 1, }, scaleMark: { width: 2, height: 10, backgroundColor: '#e5e7eb', borderRadius: 1, }, scaleMarkActive: { backgroundColor: '#7a5af8', width: 3, height: 12, }, });