import { ThemedText } from '@/components/ThemedText'; import { useThemeColor } from '@/hooks/useThemeColor'; import React, { useEffect, useRef } from 'react'; import { Animated, StyleSheet, View } from 'react-native'; import Svg, { Circle } from 'react-native-svg'; const AnimatedCircle = Animated.createAnimatedComponent(Circle); export type CalorieRingChartProps = { metabolism: number; exercise: number; consumed: number; goal: number; protein: number; fat: number; carbs: number; proteinGoal: number; fatGoal: number; carbsGoal: number; }; export function CalorieRingChart({ metabolism, exercise, consumed, goal, protein, fat, carbs, proteinGoal, fatGoal, carbsGoal, }: CalorieRingChartProps) { const surfaceColor = useThemeColor({}, 'surface'); const textColor = useThemeColor({}, 'text'); const textSecondaryColor = useThemeColor({}, 'textSecondary'); // 动画值 const animatedProgress = useRef(new Animated.Value(0)).current; // 计算还能吃多少卡路里 const remainingCalories = metabolism + exercise - consumed - goal; const canEat = Math.max(0, remainingCalories); // 计算进度百分比 (用于圆环显示) const totalAvailable = metabolism + exercise - goal; const progressPercentage = totalAvailable > 0 ? Math.min((consumed / totalAvailable) * 100, 100) : 0; // 圆环参数 - 更小的圆环以适应布局 const radius = 62; const strokeWidth = 6; const center = radius + strokeWidth; const circumference = 2 * Math.PI * radius; const strokeDasharray = circumference; // 动画效果 useEffect(() => { Animated.timing(animatedProgress, { toValue: progressPercentage, duration: 600, useNativeDriver: false, }).start(); }, [progressPercentage]); // 使用动画值计算strokeDashoffset const strokeDashoffset = animatedProgress.interpolate({ inputRange: [0, 100], outputRange: [circumference, 0], extrapolate: 'clamp', }); return ( {/* 左上角公式展示 */} 还能吃 = 代谢 + 运动 - 饮食 - 目标 {/* 主要内容区域 */} {/* 左侧圆环图 */} {/* 背景圆环 */} {/* 进度圆环 - 保持固定颜色 */} 80 ? "#FF6B6B" : "#E0E0E0"} strokeWidth={strokeWidth} fill="none" strokeDasharray={`${strokeDasharray}`} strokeDashoffset={strokeDashoffset} strokeLinecap="round" transform={`rotate(-90 ${center} ${center})`} /> {/* 中心内容 */} 还能吃 {canEat.toLocaleString()}千卡 {Math.round(progressPercentage)}% {/* 右侧数据展示 */} {/* 各项数值 */} 代谢 {metabolism.toLocaleString()}千卡 运动 {exercise}千卡 饮食 {consumed}千卡 目标 {goal}千卡 {/* 底部营养素展示 */} 蛋白质 {protein.toFixed(2)}/{proteinGoal.toFixed(2)}g 脂肪 {fat.toFixed(2)}/{fatGoal.toFixed(2)}g 碳水化合物 {carbs.toFixed(2)}/{carbsGoal.toFixed(2)}g ); } const styles = StyleSheet.create({ container: { backgroundColor: '#FFFFFF', borderRadius: 16, padding: 16, marginHorizontal: 16, marginBottom: 16, shadowColor: '#000000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.04, shadowRadius: 8, elevation: 2, }, formulaContainer: { alignItems: 'flex-start', marginBottom: 12, }, formulaText: { fontSize: 12, fontWeight: '500', color: '#999999', lineHeight: 16, }, mainContent: { width: '100%', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, paddingHorizontal: 8, }, chartContainer: { position: 'relative', alignItems: 'center', justifyContent: 'center', width: 140, flexShrink: 0, }, centerContent: { position: 'absolute', alignItems: 'center', justifyContent: 'center', }, centerLabel: { fontSize: 11, fontWeight: '500', color: '#999999', marginBottom: 2, }, centerValue: { fontSize: 16, fontWeight: '700', color: '#333333', marginBottom: 1, }, centerPercentage: { fontSize: 11, fontWeight: '500', color: '#999999', }, dataContainer: { flex: 1, marginLeft: 32, gap: 4, paddingLeft: 8, }, dataItem: { flexDirection: 'row', alignItems: 'center', gap: 4, paddingVertical: 2, }, dataIcon: { width: 6, height: 6, borderRadius: 3, }, dataLabel: { fontSize: 11, fontWeight: '500', color: '#999999', minWidth: 28, }, dataValue: { fontSize: 11, fontWeight: '600', color: '#333333', }, nutritionContainer: { flexDirection: 'row', justifyContent: 'space-between', paddingTop: 12, borderTopWidth: 1, borderTopColor: 'rgba(0,0,0,0.06)', }, nutritionItem: { alignItems: 'center', flex: 1, }, nutritionLabel: { fontSize: 10, fontWeight: '500', color: '#999999', marginBottom: 3, }, nutritionValue: { fontSize: 11, fontWeight: '600', color: '#333333', }, });